# 函数的参数


print('位置参数' + ' -----------------------------------------------------------------------------------------------------------------------')
# 定义单个参数的函数
def power(x):
    return x * x
print(power(2))  # 4

# 定义两个参数的函数
def power(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
print(power(2, 3))  # 8
# print(power(2))  # 调用失败，我们增加了一个参数，导致旧的代码因为缺少一个参数而无法正常调用


print('默认参数' + ' -----------------------------------------------------------------------------------------------------------------------')
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s


print(power(3))  # 9，使用默认参数，调用成功
print(power(3, 3))  # 27


def enroll(name, gender, age=23, city='chongqing'):
    print('name:', name)
    print('gender:', gender)
    print('age', age)
    print('city', city)


enroll('wuao', '男')
'''
name: wuao
gender: 男
age 23
city chongqing
'''
enroll('wuao', '男', 18)
'''
name: wuao
gender: 男
age 18
city chongqing
'''


# 默认参数必须指向不变对象!
# Python函数在定义的时候，默认参数L的值就被计算出来了，即[]
# 因为默认参数L也是一个变量，它指向对象[]，每次调用该函数，如果改变了L的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的[]了。
def add_end(L=[]):
    L.append('END')
    return L


print(add_end([1, 2, 3]))  # [1, 2, 3, 'END']
print(add_end(['x', 'y', 'z']))  # ['x', 'y', 'z', 'END']
print(add_end())  # ['END']
print(add_end())  # ['END', 'END']


def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return L


print(add_end())  # ['END']
print(add_end())  # ['END']


print('可变参数' + ' -----------------------------------------------------------------------------------------------------------------------')
# 可变参数之普通参数list
def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum


print(calc([1, 2, 3]))  # 14


# 定义可变参数和定义一个list或tuple参数相比，仅仅在参数前面加了一个*号。在函数内部，参数numbers接收到的是一个tuple
# 因此，函数代码完全不变。但是，调用该函数时，可以传入任意个参数，包括0个参数：
def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum


print(calc(1, 2, 3))  # 14
print(calc())  # 0

# 如果已经有一个list或者tuple，要调用一个可变参数怎么办？可以这样做：
# Python允许你在list或tuple前面加一个*号，把list或tuple的元素变成可变参数传进去
nums = [1, 2, 3]
print(calc(nums[0], nums[1], nums[2]))  # 14
print(calc(*nums))  # 14


print('关键字参数' + ' -----------------------------------------------------------------------------------------------------------------------')
# 可变参数允许你传入0个或任意个参数，这些可变参数在函数调用时自动组装为一个tuple。
# 而关键字参数允许你传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装为一个dict
def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)


person('Michael', 30)  # name: Michael age: 30 other: {}
person('Adam', 45, gender='M', job='Engineer')  # Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}

# 和可变参数类似，也可以先组装出一个dict，然后，把该dict转换为关键字参数传进去
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, city=extra['city'],
       job=extra['job'])  # Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)  # Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}


print('命名关键字参数' + ' -----------------------------------------------------------------------------------------------------------------------')
# 对于关键字参数，函数的调用者可以传入任意不受限制的关键字参数。
def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        print("has city")
        pass
    if 'job' in kw:
        # 有job参数
        print('has job')
        pass
    print('name:', name, 'age:', age, 'other:', kw)


person('Jack', 24, city='Beijing', addr='Chaoyang',
       zipcode=123456)


# has city
# Jack age: 24 other: {'city': 'Beijing', 'addr': 'Chaoyang', 'zipcode': 123456}

# 如果要限制关键字参数的名字，就可以用命名关键字参数,例如，只接收city和job作为关键字参数
def person(name, age, *, city, job):
    print(name, age, city, job)


person('Jack', 24, city='Beijing', job='Engineer')  # Jack 24 Beijing Engineer


# print(person('jack', 25, gender="M", job='engineer'))  # person() got an unexpected keyword argument 'gender'

# 如果函数定义中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个特殊分隔符*了：
def person(name, age, *args, city, job):
    print(name, age, args, city, job)


# 命名关键字参数可以有缺省值，从而简化调用：
def person(name, age, *, city='Beijing', job):
    print(name, age, city, job)


person('Jack', 24, job='Engineer')  # Jack 24 Beijing Engineer


print('参数组合' + ' -----------------------------------------------------------------------------------------------------------------------')
# 在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这5种参数都可以组合使用。
# 但是请注意，参数定义的顺序必须是：必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)


def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)


f1(1, 2)  # a = 1 b = 2 c = 0 args = () kw = {}
f1(1, 2, c=3)  # a = 1 b = 2 c = 3 args = () kw = {}
f1(1, 2, 3, 'a', 'b')  # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
f1(1, 2, 3, 'a', 'b', x=99)  # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
f2(1, 2, d=99, ext=None)  # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}

args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw)  # a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
f2(*args, **kw)  # a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

# 所以，对于任意函数，都可以通过类似func(*args, **kw)的形式调用它，无论它的参数是如何定义的。
# 虽然可以组合多达5种参数，但不要同时使用太多的组合，否则函数接口的可理解性很差。

# 小结
# Python的函数具有非常灵活的参数形态，既可以实现简单的调用，又可以传入非常复杂的参数。
#
# 默认参数一定要用不可变对象，如果是可变对象，程序运行时会有逻辑错误！
#
# 要注意定义可变参数和关键字参数的语法：
#
# *args是可变参数，args接收的是一个tuple；
#
# **kw是关键字参数，kw接收的是一个dict。
#
# 以及调用函数时如何传入可变参数和关键字参数的语法：
#
# 可变参数既可以直接传入：func(1, 2, 3)，又可以先组装list或tuple，再通过*args传入：func(*(1, 2, 3))；
#
# 关键字参数既可以直接传入：func(a=1, b=2)，又可以先组装dict，再通过**kw传入：func(**{'a': 1, 'b': 2})。
#
# 使用*args和**kw是Python的习惯写法，当然也可以用其他参数名，但最好使用习惯用法。
#
# 命名的关键字参数是为了限制调用者可以传入的参数名，同时可以提供默认值。
#
# 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*，否则定义的将是位置参数。
